package com.liunian;

import com.liunian.annotation.WrpcApi;
import com.liunian.channelhandler.handler.MethodCallHandler;
import com.liunian.channelhandler.handler.WrpcRequestDecoder;
import com.liunian.channelhandler.handler.WrpcResponseEcoder;
import com.liunian.config.Configuration;
import com.liunian.discovery.RegistryConfig;
import com.liunian.heartbeatdetect.HeartbeatDetector;
import com.liunian.loadbalancer.LoadBalancer;
import com.liunian.transport.message.WrpcRequest;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LoggingHandler;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.net.InetSocketAddress;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

@Slf4j
public class WrpcBootstrap {

    // WrpcBootstrap是个单例，我们希望每个应用程序只有一个实例
    private static final WrpcBootstrap wrpcBootstrap = new WrpcBootstrap();

    // 全局的配置中心
    private Configuration configuration;

    // 保存requet对象，可以到当前线程中随时获取
    public static final ThreadLocal<WrpcRequest> REQUEST_THREAD_LOCAL = new ThreadLocal<>();

    // 连接的缓存,如果使用这样的类----InetSocketAddress 做key一定要看它有没有重写equals方法和toString方法
    public final static Map<InetSocketAddress, Channel> CHANNEL_CACHE = new ConcurrentHashMap<>(16);
    public final static TreeMap<Long, Channel> ANSWER_TIME_CHANNEL_CACHE = new TreeMap<>();

    // 维护已经发布且暴露的服务列表 key -> interface 的全限定名。 value -> ServiceConfig<?>
    public final static Map<String, ServiceConfig<?>> SERVICE_LIST = new ConcurrentHashMap<>(16);

    // 定义全局的对外挂起的 completableFuture
    public final static Map<Long, CompletableFuture<Object>> PENDING_REQUEST = new ConcurrentHashMap<>(128);

    // 维护一个zookeeper实例
//    private ZooKeeper zookeeper;

    private WrpcBootstrap() {
        // 构造启动引导程序时，需要做一些初始化
        configuration = new Configuration();

    }

    public static WrpcBootstrap getInstance() {
        return wrpcBootstrap;
    }

    /**
     * 这个方法用来定义当前应用的名字
     */
    public WrpcBootstrap application(String appName) {
        configuration.setAppName(appName);
        return this;
    }

    /**
     * 这个方法用来配置一个注册中心
     *
     * @param registryConfig：可能是zookeeper,nacos等
     */
    public WrpcBootstrap registry(RegistryConfig registryConfig) {
        // 这里维护一个zookeeper实例，但是如果这样写就会将zookeeper与当前工程耦合，、
        // 我们其实更希望以后可以扩展更多种不同的实现

        // 尝试使用 registryConfig 获取一个注册中心 。 有点类似与工厂方法
        configuration.setRegistryConfig(registryConfig);
        return this;
    }

    /**
     * 配置一个负载均衡策略
     * @param loadBalancer 负载均衡策略
     * @return
     */
    public WrpcBootstrap loadBalancer(LoadBalancer loadBalancer) {
        configuration.setLoadBalancer(loadBalancer);
        return this;
    }

    /**
     * 发布服务的核心：将接口以及匹配的实现注册到服务中心。
     * 具体的服务的发布
     *
     * @param service 封装的需要发布的服务
     */
    public WrpcBootstrap publish(ServiceConfig<?> service) {
        // 抽象了注册中心的概念，使用注册中心的一个实现完成注册
        configuration.getRegistryConfig().getRegistry().register(service);
        // 当服务调用方通过接口、方法名、具体的方法参数列表发起调用，提供方怎么知道使用哪个实现？
        // 1、new 一个 2、spring beanFactory。getBean(Class) 3、自己维护一个映射关系
        SERVICE_LIST.put(service.getInterface().getName(), service);
        return this;
    }

    /**
     * 批量发布服务
     *
     * @param services 封装的需要发布的服务集合
     */
    public WrpcBootstrap publish(List<ServiceConfig<?>> services) {
        for (ServiceConfig<?> service : services) {
            this.publish(service);
        }
        return this;
    }

    /**
     * 启动 netty 服务
     */
    public void start() {
        // 1. 创建eventLoopGroup,老板只负责处理请求，之后会将请求分发给worker
        // EventLoopGroup 相当于线程池
        EventLoopGroup boss = new NioEventLoopGroup(2);
        EventLoopGroup worker = new NioEventLoopGroup(10);

        try {
            // 2.需要一个服务器引导程序ServerBootStrap
            ServerBootstrap serverBootstrap = new ServerBootstrap();

            // 3.配置服务器
            serverBootstrap = serverBootstrap.group(boss, worker)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        // 这个地方是核心，我们需要添加很多入站和出站的handler。
                        @Override
                        protected void initChannel(SocketChannel socketChannel) {
                            socketChannel.pipeline().addLast(new LoggingHandler())
                                    .addLast(new WrpcRequestDecoder())
                                    // 根据请求进行方法调用。
                                    .addLast(new MethodCallHandler())
                                    .addLast(new WrpcResponseEcoder());
                        }
                    });
            // 4. 绑定端口
            ChannelFuture channelFuture = serverBootstrap.bind(configuration.getPort()).sync();
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            try {
                boss.shutdownGracefully().sync();
                worker.shutdownGracefully().sync();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }


    /**
     * ------------------------------------------------服务调用方相关api-------------------------------------------------------
     */

    public WrpcBootstrap reference(ReferenceConfig<?> reference) {
        // 开启对这个服务的心跳检测
        HeartbeatDetector.detectHeartbeat(reference.getInterface().getName());
        // 在这个方法里我们是否可以拿到相关的配置项--注册中心
        // 配置reference，将来调用get方法时，方便生成代理对象。
        // reference 需要一个注册中心
        reference.setRegistry(this.configuration.getRegistryConfig().getRegistry());
        // 设置分组
        reference.setGroup(this.configuration.getGroup());
        return this;
    }

    /**
     * 配置序列化的方式
     *
     * @param serializeType 序列化的方式
     */
    public WrpcBootstrap serialize(String serializeType) {
        configuration.setSerializeType(serializeType);
        if (log.isDebugEnabled()) {
            log.debug("我们配置了使用的序列化的方式为[{}].", serializeType);
        }
        return this;
    }

    public WrpcBootstrap compress(String compressType) {
        configuration.setCompressType(compressType);
        if (log.isDebugEnabled()) {
            log.debug("我们配置了使用的压缩算法为[{}].", compressType);
        }
        return this;
    }

    /**
     * 扫包，进行批量注册
     * @param packageName 包名
     * @return this
     */
    public WrpcBootstrap scan(String packageName) {
        // 1、需要通过packageName获取其下的所有的类的全限定名称
        List<String> classNames = getAllClassNames(packageName);
        // 2、通过反射获取它的接口，构建具体的实现
        List<Class<?>> classes = classNames.stream().map(className -> {
            try {
                return Class.forName(className);
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
        }).filter(clazz -> clazz.getAnnotation(WrpcApi.class) != null)
                .collect(Collectors.toList());

        for (Class<?> clazz : classes) {
            // 获取它的接口
            Class<?>[] interfaces = clazz.getInterfaces();
            Object instance = null;
            try {
                instance = clazz.getConstructor().newInstance();
            } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
                throw new  RuntimeException(e);
            }

            // 获取分组信息
            WrpcApi wrpcApi = clazz.getAnnotation(WrpcApi.class);
            String group = wrpcApi.group();

            for (Class<?> anInterface : interfaces) {
                ServiceConfig<?> serviceConfig = new ServiceConfig<>();
                serviceConfig.setInterface(anInterface);
                serviceConfig.setRef(instance);
                serviceConfig.setGroup(group);
                if (log.isDebugEnabled()) {
                    log.debug("----->已经通过扫包将服务[{}]发布.", anInterface);
                }
                // 发布
                publish(serviceConfig);
            }

        }
        return this;
    }

    private List<String> getAllClassNames(String packageName) {
        // 1、通过packageName获得绝对路径
        // com.liunian.xxx.yyy -> d://dssdf/werwe/com/liunian/xxx/yyy
        String basePath = packageName.replaceAll("\\.","/");
        URL url = ClassLoader.getSystemClassLoader().getResource(basePath);
        if (url == null) {
            throw new RuntimeException("包扫描时，发现路径不存在。");
        }
        String absolutePath = url.getPath();

        //
        List<String> classNames = new ArrayList<>();
        classNames = recursionFile(absolutePath,classNames,basePath);

        return classNames;
    }

    /**
     * 递归处理文件获得所有的类
     * @param absolutePath
     * @param classNames
     * @return
     */
    private List<String> recursionFile(String absolutePath, List<String> classNames, String basePath) {
        // 获取文件
        File file = new File(absolutePath);
        // 判断文件是否是文件夹
        if (file.isDirectory()) {
            // 找到文件夹的所有的文件
            File[] children = file.listFiles(pathname -> pathname.isDirectory() || pathname.getPath().contains(".class"));
            if (children == null || children.length == 0) {
                return classNames;
            }
            for (File child : children) {
                if (child.isDirectory()) {
                    // 递归调用
                    recursionFile(child.getAbsolutePath(), classNames, basePath);
                } else {
                    // 文件 -> 类的全限定名称
                    String className = getClassNameByAbsolutePath(child.getAbsolutePath(),basePath);
                    classNames.add(className);
                }
            }
        } else {
            // 文件 -> 类的全限定名称
            String className = getClassNameByAbsolutePath(absolutePath,basePath);
            classNames.add(className);
        }
        return classNames;
    }

    /**
     * 将类的绝对路径变成com\liunian\...\.class，再把这部分的转化成 com.liunian. ... . .class
     * @param absolutePath
     * @return
     */
    private String getClassNameByAbsolutePath(String absolutePath, String basePath) {
        String fileName = absolutePath.substring(absolutePath.indexOf(basePath.replaceAll("/","\\\\")))
                .replaceAll("\\\\",".");
        fileName = fileName.substring(0,fileName.indexOf(".class"));
        return fileName;
    }

    public Configuration getConfiguration() {
        return configuration;
    }

    public WrpcBootstrap group(String group) {
        this.getConfiguration().setGroup(group);
        return this;
    }
}
